// *********************************************************
//      Copyright (c) Kosta Gaitanis
//      All rights reserved
//
//      k.gaitanis@gmail.com
//
// *********************************************************

#ifndef ITEM_H
#define ITEM_H

#include "Matrix.h"
#include "Face.h"
#include "Texture.h"
#include "Material.h"
#include <QMetaType>

class Item;
class Camera;
typedef QList<Face*> FaceList;
typedef QList<Item*> ItemList;

/**
 * \class Item
 * \brief Represents an item in openGL. An item is comprised of a certain number
 *        of faces and is positioned in space according to a transformation matrix.
 * \author Kosta Gaitanis
 *
 * An item can contain children items, each one with their own transformation matrix.
 * When painting an item, it also paints all of its children.
 */
class Item
{
public:
    /**
     * Constructs a new empty item with the given name.
     * The transformation matrix is set to the identity.
     *
     * @param name The name of this item.
     */
    Item(const QString& name = QString());

    /**
     * Destructor.
     * Also deletes all the children.
     * Textures are not deleted.
     */
    virtual ~Item();

    /**
     * @return the name of this item.
     */
    const QString& getName() const;

    /**
     * Sets the name of this item.
     * @param name The new name.
     */
    void setName(const QString& name);

    /**
     * Adds the item to the list of children.
     * The parent takes ownership of the child and will delete it when the parent
     * is deleted.
     *
     * @param item The item that will be copied and added as a child.
     */
    void addChild(Item* item);

    /**
     * Deletes the child named name. If more than one children have the same
     * name, only the child that was added first will be deleted.
     *
     * @param name The name of the child to be removed.
     */
    void deleteChild(const QString& name);

    /**
     * Removes the given child from the list of children without deleting it.
     *
     * @param child The child to be removed.
     */
    void removeChild(Item *child);

    /**
     * @param child A pointer to an item
     * @param recursive Set to true to also check inside child items.
     * @return True if child was found.
     */
    bool hasChild(Item* child, bool recursive = false) const;

    /**
     * @param name The name of the child item.
     * @param recursive Set to true to also check inside child items.
     * @return True if a child with the given name was found.
     */
    bool hasChild(const QString& name, bool recursive = false) const;

    /**
     * @return A list of QStrings with the names of all the children of this item.
     */
    QStringList getChildrenNames() const;

    /**
     * @return The list of pointers to the children of this item.
     */
    ItemList getChildren(bool recursive = false) const;

    /**
     * Returns a pointer to the child with the given name.
     * 0 is returned if no child has such a name.
     * If more than one children have the same name, the child that was added
     * first is returned.
     *
     * @param name The name of the child.
     * @return A pointer to the child item. 0 if not found.
     */
    Item* getChild(const QString& name) const;

    /**
     * @return An array of 16 GLdoubles representing this item's transform.
     */
    const GLdouble *getGLTransform() const;

    /**
     * @return A Matrix representing this item's transform.
     */
    Matrix getTransform() const;

    /**
     * Sets the transform to be applied to this item.
     *
     * @param glTransform An array of 16 GLdoubles.
     */
    void setTransform(const GLdouble *glTransform);

    /**
     * @brief same as calling setGLTransform() with an array of GLdoubles
     * @param transform A matrix
     */
    void setTransform(const Matrix& transform);

    /**
     * Adds a face to this item.
     *
     * @param face The face to be added.
     */
    void addFace(const Face& face);

    /**
     * @return The number of faces of this item.
     */
    int count() const;

    /**
     * @return True if this item has no faces.
     */
    bool isEmpty() const;

    /**
     * @brief Called once at initialization. Used to generate display list id.
     */
    void initializeGL();

    /**
     * Paints this item.
     * This function does not verify the validity of the current GL context.
     * Sets the dirty flag to false.
     */
    virtual void paint(Camera* camera);

    /**
     * Returns true if this item has been modified
     * since the last paint() call.
     * This means that the GLDisplayList needs refreshing.
     *
     * Modification that require refresh are :
     *  - Adding/removing faces.
     *  - Adding/removing children.
     *
     * Changing the transform of this item, its material or its texture
     * does not require the display list to be refreshed.
     */
    bool isDirty() const;

    /**
     * Sets the texture that will be used to render this item.
     * The texture object should not be deleted while assigned to any item.
     * Use setTexture(0) to unset textures for this item.
     *
     * @param texture A pointer to a texture.
     */
    virtual void setTexture(const Texture* texture);

    /**
     * @return A const pointer to the texture of this item.
     */
    const Texture* getTexture() const;

    /**
     * @brief Assigns a material to this item.
     * @param material The material to be attached to this item.
     */
    void setMaterial(const Material& material);

    /**
     * @return The material of this item.
     */
    Material* getMaterial();

    /**
     * @brief sets the dirty flag to true meaning that this item will
     * regenerate it's display list next time it is painted.
     */
    void setDirty();

    /**
     * @brief sets the current frame. Used for animating items. Calls the virtual
     * method onFrameChanged().
     * @param frame the current frame
     */
    void setFrame(double frame);

    /**
     * @return The current frame.
     */
    double getFrame() const;

    /**
     * @param visible True to make this item visible, false to hide it.
     */
    void setVisible(bool visible = true);

    /**
     * @return True if this item is visible.
     */
    bool isVisible() const;

    /**
     * @brief This returns the 'position' of this item in global coordinates.
     *
     * This is used to align the camera.
     *
     * @return The current position of this item.
     */
    Vector getCurrentPosition() const;

    Matrix getGlobalTransform() const;

    void setShareMaterialWithParent(bool share);

protected:
    /**
     * @brief Called whenever the current frame has changed.
     * Current implementation does nothing. Reimplement in a subclass to provide
     * animation for your item.
     * @param frame The current frame.
     */
    virtual void onFrameChanged(double frame);

private:
    /**
     * Can not create items on the stack. Only pointers are allowed.
     */
    Q_DISABLE_COPY(Item)

    bool m_dirty;                       /// > True if the display list needs refresh.
    QString m_name;                     /// > The name of this item.
    GLdouble m_glTransform[16];         /// > The transformation matrix.
    Matrix m_transform;                 /// > The transformation matrix.
    FaceList m_faces;                   /// > The list of faces.
    Item* m_parent;                     /// > The parent item.
    ItemList m_children;            /// > The child items.
    GLuint m_displayListId;             /// > The display list unique id.
    const Texture* m_texture;           /// > The texture.
    Material m_material;                /// > The material of this item.
    bool m_shareMaterialWithParent;

    double m_frame;                     /// > The current frame.
    bool m_visible;                     /// > True if this item is visible.
};

Q_DECLARE_METATYPE(const Item*)

#endif // ITEM_H
